#include "wifis.h"
#include "mqtts.h"
#include "assert.h"
#include "taskpool.h"
#include "main.h"
#include "assitant.h"
#include "LittleFS.h"
#include "hashlib.h"

#define fs LittleFS

String bf_client_id = "";
String bf_pub_topic = "";
extern void (*_rep_data_hook)(String &data_str);
Li_List bf_listener = NULL;

static WiFiClient espClient;
PubSubClient bf_mqtt_client(espClient);
bool _bf_ok = false;

void bf_filter_from(String &data_str);
void bf_mqtt_setup();
int bf_mqtt_reconnect();
int bf_mqtt_loop();
int cmd_bf_set_token(int n, char *args[]);
int remove_bf_listener(int n, char *args[]);
int cmd_bf_listen_topic(int n, char *args[]);
int cmd_bf(int n, char *args[]);
struct BFListener_t
{
    HASH topic_hs;
    HASH topic_key;
    char *cmds;
};
typedef BFListener_t *BFListener;

void bf_init()
{
    public_a_cmd_link("bf", cmd_bf);
    bf_listener = new_li(BFListener);
}

int cmd_bf(int n, char *args[])
{
    if (n < 2)
    {
        li_error("usage:bf <mode> [args]...\n available modes are connect, listen, remove", -__LINE__);
    }
    if (strcmp(args[1], "connect") == 0)
    {
        return cmd_bf_set_token(n - 1, args + 1);
    }
    if (strcmp(args[1], "listen") == 0)
    {
        return cmd_bf_listen_topic(n - 1, args + 1);
    }
    if (strcmp(args[1], "remove") == 0)
    {
        return remove_bf_listener(n - 1, args + 1);
    }
    li_error("usage:bf <mode> [args]...\n available modes are connect, listen, remove", -__LINE__);
}

int cmd_bf_set_token(int n, char *args[])
{
    if (n != 3)
    {
        li_error("bad arguments\n usage: bf connect token topic\n", -__LINE__);
    }
    if (_bf_ok)
    {
        if (args[1][0] != '-')
        {
            bf_client_id = args[1];
        }
        if (args[2][0] != '-')
        {
            bf_pub_topic = args[2];
        }
        if (bf_mqtt_client.connected())
        {
            bf_mqtt_client.disconnect();
        }
        return 0;
    }

    bf_client_id = args[1];
    bf_pub_topic = args[2];

    bf_mqtt_setup();
    _bf_ok = true;
    if (bf_mqtt_client.connected())
        return 0;
    li_error("bemfa not connect, will continually try connecting later", -__LINE__);
}

int cmd_bf_listen_topic(int n, char *args[])
{
    if (n < 4)
    {
        li_error("bad arg\nusage:bf listen <topic> <keyword> <cmd>...", -__LINE__);
    }
    assert_msg(_bf_ok, "bemfa cloud not set up\n try: bf connect <token> <topic>");
    assert_msg(bf_mqtt_client.connected(), "bemfa cloud not connect");
    var ok = bf_mqtt_client.subscribe(args[1]);
    assert_msg(ok, "failed to subscribe topic");

    BFListener listener = new BFListener_t;
    listener->topic_hs = getHashCodeS(args[1]);
    listener->topic_key = getHashCodeS(args[2]);

    int len = 0;
    for (int i = 3; i < n; i++)
    {
        len += strlen(args[i]) + 1;
    }
    char *cmd = new char[len];
    assert_msg(cmd, "no memory");
    listener->cmds = cmd;
    int b_len = 0;
    for (int i = 3; i < n; i++)
    {
        b_len = strlen(args[i]);
        memcpy(cmd, args[i], b_len);
        cmd += b_len;
        *cmd = ' ';
        cmd++;
    }
    (listener->cmds)[len] = 0;
    li_add(bf_listener, listener);
    char tx[128];
    sprintf(tx, "%x,%x,%s\n", listener->topic_hs, listener->topic_key, listener->cmds);
    lily_out(tx);
    return 0;
}

int remove_bf_listener(int n, char *args[])
{
    if (n < 2)
    {
        li_error("bad arg\nusage:bf remove <topic> [keyword]", -__LINE__);
    }
    HASH hs = getHashCodeS(args[1]);
    HASH kw_hs = 0;
    if (n > 2)
    {
        kw_hs = getHashCodeS(args[2]);
    }
    n = bf_listener->count;
    BFListener *listener = list_content(bf_listener, BFListener);
    int count = 0;
    for (int i = 0; i < n; i++)
    {
        if (listener[i]->topic_hs != hs)
            continue;
        if (kw_hs && listener[i]->topic_key != kw_hs)
            continue;

        delete listener[i]->cmds;
        delete listener[i];
        listener[i] = listener[n - 1];
        n--;
        count++;
    }
    return count;
}
void bf_mqtt_callback(char *topic, byte *payload, unsigned int length)
{
    int n = bf_listener->count;
    HASH hs = getHashCodeS(topic);
    HASH kw_hs = getHashCode(payload, length);
    BFListener *listener = list_content(bf_listener, BFListener);
    for (int i = 0; i < n; i++)
    {
        if (listener[i]->topic_hs != hs)
            continue;
        if (listener[i]->topic_key != kw_hs)
            continue;
        lily_do(listener[i]->cmds);
    }
}

void bf_filter_from(String &data_str)
{
    String bs("");
    const char *ks[] = {"\"temperature\":", "\"humidity\":", "\"io\":", "\"light\":"};
    for (int i = 0; i < sizeof(ks) / (sizeof(*ks)); i++)
    {
        int ind = data_str.indexOf(ks[i]);
        bs += "#";
        if (ind < 0)
        {
            continue;
        }
        int next_from = ind + strlen(ks[i]);
        float val = atof(data_str.c_str() + next_from);
        if (i == 2) // io
        {
            if (val)
                bs = bs + "on";
            else
                bs = bs + "off";
            continue;
        }
        bs = bs + val;
    }
    bf_mqtt_client.publish(bf_pub_topic.c_str(), bs.c_str(), bs.length());
}
void bf_mqtt_setup()
{
    bf_mqtt_client.setServer("bemfa.com", 9501);
    bf_mqtt_client.setCallback(bf_mqtt_callback);
    bf_mqtt_client.setKeepAlive(60);
    bf_mqtt_client.connect(bf_client_id.c_str(), "u", "p");
    public_a_timer(bf_mqtt_loop, Hz(11));
    _rep_data_hook = bf_filter_from;
}

int bf_mqtt_reconnect()
{
    if (!WiFi.isConnected())
    {
        return -1;
    }
    var _in_ticking = stop_schedule();
    if (bf_mqtt_client.connect(bf_client_id.c_str(), "u", "p"))
    {
        remove_timer(bf_mqtt_reconnect);
        public_a_timer(bf_mqtt_loop, Hz(11));
        if (_in_ticking)
            resume_schedule();
        return 0;
    }
    xout_("bf mqtt connect failed, rc=");
    xouts(bf_mqtt_client.state());
    if (_in_ticking)
        resume_schedule();
    return -2;
}

int bf_mqtt_loop()
{
    if (!bf_mqtt_client.connected())
    {
        var ok = bf_mqtt_reconnect();
        if (ok < 0)
        {
            remove_timer(bf_mqtt_loop);
            if (!had_timer(bf_mqtt_reconnect))
                public_a_timer(bf_mqtt_reconnect, Second(60));
        }
        return -1;
    }
    bf_mqtt_client.loop();
    return 0;
}
